{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "635d8ebb",
   "metadata": {},
   "source": [
    "# Conversation With History\n",
    "\n",
    "- Author: [3dkids](https://github.com/3dkids), [Joonha Jeon](https://github.com/realjoonha)\n",
    "- Peer Review : [Teddy Lee](https://github.com/teddylee777), [Shinar12](https://github.com/Shinar12), [Kenny Jung](https://www.linkedin.com/in/kwang-yong-jung), [Sunyoung Park (architectyou)](https://github.com/Architectyou)\n",
    "- Proofread : [Juni Lee](https://www.linkedin.com/in/ee-juni)\n",
    "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/05-Memory/10-Conversation-With-History.ipynb)\n",
    "[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/05-Memory/10-Conversation-With-History.ipynb)\n",
    "\n",
    "## Overview\n",
    "\n",
    "This tutorial covers how to create a multi-turn ```Chain``` that remembers previous conversations, using LangChain.<br>\n",
    "It includes managing conversation history, defining a ```ChatPromptTemplate```, and utilizing an LLM for chain creation.<br>\n",
    "The conversation history is managed using ```chat_history```.\n",
    "\n",
    "\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environment Setup](#environment-setup)\n",
    "- [Creating a Chain that Remembers Previous Conversations](#creating-a-chain-that-remembers-previous-conversations)\n",
    "- [Creating a Chain to Record Conversations](#creating-a-chain-to-record-conversations-chain_with_history)\n",
    "\n",
    "### References\n",
    "\n",
    "- [LangChain: MessagesPlaceholder](https://python.langchain.com/docs/concepts/prompt_templates/#messagesplaceholder)\n",
    "- [LangChain: chatmessagehistory](https://python.langchain.com/docs/versions/migrating_memory/chat_history/#chatmessagehistory)\n",
    "- [LangChain: runnablewithmessagehistory](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.history.RunnableWithMessageHistory.html#runnablewithmessagehistory)\n",
    "----"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c6c7aba4",
   "metadata": {},
   "source": [
    "## Environment Setup\n",
    "\n",
    "Set up the environment. You may refer to [Environment Setup](https://wikidocs.net/257836) for more details.\n",
    "\n",
    "**[Note]**\n",
    "- ```langchain-opentutorial``` is a package that provides a set of easy-to-use environment setup, useful functions and utilities for tutorials. \n",
    "- You can checkout the [```langchain-opentutorial```](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "21943adb",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain-opentutorial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f25ec196",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "package.install(\n",
    "    [\"langchain_core\", \"langchain_community\", \"langchain_openai\"],\n",
    "    verbose=False,\n",
    "    upgrade=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "7f9065ea",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Environment variables have been set successfully.\n"
     ]
    }
   ],
   "source": [
    "# Set environment variables\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "set_env(\n",
    "    {\n",
    "        \"OPENAI_API_KEY\": \"\",\n",
    "        \"LANGCHAIN_API_KEY\": \"\",\n",
    "        \"LANGCHAIN_TRACING_V2\": \"true\",\n",
    "        \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n",
    "        \"LANGCHAIN_PROJECT\": \"ConversationWithHistory\",\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfc3a618",
   "metadata": {},
   "source": [
    "Alternatively, environment variables can also be set using a ```.env``` file.\n",
    "\n",
    "**[Note]**\n",
    "\n",
    "- This is not necessary if you've already set the environment variables in the previous step."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "790e32ad",
   "metadata": {},
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa00c3f4",
   "metadata": {},
   "source": [
    "## Creating a Chain that Remembers Previous Conversations\n",
    "\n",
    "```MessagesPlaceholder``` is a class in LangChain used to handle conversation history. It is primarily utilized in chatbots or multi-turn conversation systems to store and reuse previous conversation content.\n",
    "\n",
    "### Key Roles  \n",
    "**Inserting Conversation History** :  \n",
    "- Used to insert prior conversations (e.g., question-and-answer history) into the prompt.  \n",
    "- This allows the model to understand the context of the conversation and generate appropriate responses.  \n",
    "\n",
    "**Managing Variables** :  \n",
    "- Manages conversation history within the prompt using a specific key (e.g., ```chat_history```).  \n",
    "- It is linked to a user-defined variable name.  \n",
    "\n",
    "### Usage  \n",
    "```MessagesPlaceholder(variable_name=\"chat_history\")```  \n",
    "- Here, ```chat_history``` is the variable name where conversation history is stored.  \n",
    "- As the conversation progresses, ```chat_history``` is continually updated with pairs of questions and responses.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "69cb77da",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "from langchain_community.chat_message_histories import ChatMessageHistory\n",
    "from langchain_core.chat_history import BaseChatMessageHistory\n",
    "from langchain_core.runnables.history import RunnableWithMessageHistory\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "\n",
    "\n",
    "# Define the prompt.\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"You are a Question-Answering chatbot. Please provide answers to the given questions.\",\n",
    "        ),\n",
    "        # Use \"chat_history\" as the key for conversation history without modifying it if possible.\n",
    "        MessagesPlaceholder(variable_name=\"chat_history\"),\n",
    "        (\"human\", \"#Question:\\n{question}\"),  # Use user input as a variable.\n",
    "    ]\n",
    ")\n",
    "\n",
    "# Create the LLM.\n",
    "llm = ChatOpenAI(model_name=\"gpt-4o\")\n",
    "\n",
    "# Create a basic chain.\n",
    "chain = prompt | llm | StrOutputParser()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2b2fc536",
   "metadata": {},
   "source": [
    "## Creating a Chain to Record Conversations (```chain_with_history```)\n",
    "\n",
    "In this step, we create a system that manages **session-based conversation history** and generates an **executable chain**.\n",
    "\n",
    "- **Conversation History Management** : The ```store``` dictionary saves and retrieves conversation history (```ChatMessageHistory```) by session ID. If a session does not exist, a new one is created.  \n",
    "- **Chain Execution** : ```RunnableWithMessageHistory``` combines conversation history and the chain to generate responses based on user questions and conversation history. This structure is designed to effectively manage multi-turn conversations.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "1b78d33f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# A dictionary to store session history.\n",
    "store = {}\n",
    "\n",
    "\n",
    "# A function to retrieve session history based on the session ID.\n",
    "def get_session_history(session_ids):\n",
    "    print(f\"[Conversation session ID]: {session_ids}\")\n",
    "    if session_ids not in store:  # When the session ID is not in the store.\n",
    "        # Create a new ChatMessageHistory object and save it in the store.\n",
    "        store[session_ids] = ChatMessageHistory()\n",
    "    return store[session_ids]  # Return the session history for the given session ID."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "ee2ef6a3",
   "metadata": {},
   "outputs": [],
   "source": [
    "chain_with_history = RunnableWithMessageHistory(\n",
    "    chain,\n",
    "    get_session_history,  # A function to retrieve session history.\n",
    "    input_messages_key=\"question\",  # The key where the user's question will be inserted into the template variable.\n",
    "    history_messages_key=\"chat_history\",  # The key for the message in the history.\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9e4d831",
   "metadata": {},
   "source": [
    "Execute the first question."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ba136eaa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Conversation session ID]: abc123\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "\"Hello, Teddy! Do you have a question or something specific you'd like to discuss?\""
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain_with_history.invoke(\n",
    "    # Question input.\n",
    "    {\"question\": \"My name is Teddy.\"},\n",
    "    # Record conversations based on the session ID.\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81ad40c4",
   "metadata": {},
   "source": [
    "Execute the next question."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "0874c14b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Conversation session ID]: abc123\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'Your name is Teddy.'"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain_with_history.invoke(\n",
    "    # Question input.\n",
    "    {\"question\": \"What's my name?\"},\n",
    "    # Record conversations based on the session ID.\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c38004a",
   "metadata": {},
   "source": [
    "Below is a case where a new session is created when the ```session_id``` is different."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "a2d22b26",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Conversation session ID]: abc1234\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "\"I'm sorry, but I don't have access to personal information about you, including your name.\""
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain_with_history.invoke(\n",
    "    # Question input.\n",
    "    {\"question\": \"What's my name?\"},\n",
    "    # Record conversations based on the session ID.\n",
    "    config={\"configurable\": {\"session_id\": \"abc1234\"}},\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
